//////////////////////////////////////////////////////////////////////////////////
//
// ActionScript 3.0 Common Library (MIT License)
// Copyright (c) 2009 AS3CommonCode | Tony Birleffi | http://www.mosaiceye.com
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////////

package org.as3commoncode.utils 
{
	/**
	 * This is a utility class with many useful methods for math.
	 * 
	 * @author Tony Birleffi
	 */
	public class MathUtil 
	{
		/**
		 * Converts a number from degrees to radians.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.degreesToRadians(50);
		 * </listing>
		 * 
		 * @param d		degree.
		 * @return Number.
		 */
		public static function degreesToRadians(d:Number):Number 
		{
			return d * (Math.PI / 180);
		}
		
		/**
		 * Converts a number from radians to degrees.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.radiansToDegrees(50);
		 * </listing>
		 * 
		 * @param r		radian.
		 * @return Number.
		 */
		public static function radiansToDegrees(r:Number):Number 
		{
			return r * (180 / Math.PI);
		}
		
		/**
		 * Convert a number from a percentage to degrees, based on 360°.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.percentToDegrees(50);
		 * </listing>
		 * 
		 * @param n		percentage.
		 * @return Number.
		 */
		public static function percentToDegrees(n:Number):Number 
		{
			return (Math.abs(n / 100)) * 360;		
		}
		
		/**
		 * Convert a number from degrees to a percentage, based on 360°.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.degreesToPercent(50);
		 * </listing>
		 * 
		 * @param n		degrees.
		 * @return Number.
		 */
		public static function degreesToPercent(n:Number):Number 
		{
			return (Math.abs(n / 360)) * 100;		
		}
		
		/**
		 * Round a number. Specifying a roundToInterval rounds to the nearest interval.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.round(49.95, 1);
		 * </listing>
		 * 
		 * @param number				number you want to round.
		 * @param roundToInterval		[Optional] The integer to round the number, defaults to 1.
		 * @return Number.
		 */
	    public static function round(number:Number, roundToInterval:Number = 1):Number
		{
			return Math.round(number / roundToInterval) * roundToInterval;
	    }
	
	    /**
		 * Floor a number. Specifying a roundToInterval, floors to the nearest interval.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.floor(49.95, 1);
		 * </listing>
		 * 
		 * @param number              number you want to floor.
		 * @param roundToInterval	  [Optional] The interval to floor the number, defaults to 1.
	     * @return Number.
	     */
		public static function floor(number:Number, roundToInterval:Number = 1):Number
		{
			return Math.floor(number / roundToInterval) * roundToInterval;
	    }
	
	    /**
	     * Ceiling a number. Specifying a roundToInterval, ceilings the interval.
	     * 
	     * @example
		 * <listing>
		 * MathUtil.ceil(49.95, 1);
		 * </listing>
		 * 
	     * @param number              number you want to ceil.
		 * @param roundToInterval	  [Optional] The interval to which you want to ceil the number, defaults to 1.
	     * @return Number.
	     */
	    public static function ceil(number:Number, roundToInterval:Number = 1):Number
		{
			return Math.ceil(number / roundToInterval) * roundToInterval;
	    }
		
		/**
		 * Calculates a random number within a minimum and maximum range. Specifying a roundToInterval, ceilings the interval.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.random(0, 5, 1);
		 * </listing>
		 * 
		 * @param min		value for the bottom range.
		 * @param max		value for the upper range.
		 * @return	Number.
		 */
		public static function random(min:Number = 0, max:Number = 0, roundToInterval:Number = 1):Number
		{	
			// If the minimum is greater than the maximum, switch the two.
			if(min > max)
			{
				var temp:Number = min;
				min = max;
				max = temp;
			}
			
			// Calculate the range by subtracting the minimum from the maximum. Add 1 times the round to interval to ensure even distribution.
			var deltaRange:Number = (max - min) - roundToInterval;
	
			// Multiply the range by Math.random(). Generates a random number in the range, it won't be offset properly, nor will it be rounded to the correct number of places.
			var randomNumber:Number = Math.random() * deltaRange;
	
			// Add the minimum to the random offset to generate a random number in the correct range.
			randomNumber += min;
	
			// Return the random value. Use the custom floor() method to ensure the result is rounded to the proper number of decimal places.
			return round(randomNumber, roundToInterval);
		}
		
		/**
		 * This crazy little algorithm will return an array of consecutive random numbers in a given set.
		 * For example if we give a min of 0 and a max of 20 and we want an array of 10 integers  we would get something back like this: [2,4,5,6,7,12,13,15,16,19]
		 * The numbers will be consecutive and will not repeat. This was invented by Donald Knuth in the 70's. Sexy eh?
		 *
		 * @example
		 * <listing>
		 * MathUtil.knuthArray(0, 5, 1);
		 * </listing>
		 * 
		 * @param	min			minimum number
		 * @param	max			maximum number
		 * @param	select		number of integers we wish to return inside the array, the length of the desired array.
		 * @return	Array.
		 */
		public static function knuthArray(min:Number, max:Number, select:Number):Array
		{
	 		var array:Array = [];
			var M:Number = max;
			var N:Number = select;
		
			for (var i:Number = min; i < M && N > min; ++i)
			{
				if(random(M - i) < N)
				{
					array.push(i);
					N--;
				}
			}
			
			return array;
		}
		
		/**
		 * Calculates the factorial of the passed-in number.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.factorial(50);
		 * </listing>
		 * 
		 * @param number	the number to calculate the factorial.
		 * @return Number.
		 */
		public static function factorial(number:Number):Number
		{
			if (number == 0) return 1;
			var n:Number = number;
			var l:Number = n - 1;
			while(l)
			{
				n = n * l;
				l--;
			}
			return n;
		}	
		
		/**
		 * Rips through an indexed array of numbers adding the total of all values.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.sums([ 50, 30, 20 ]);
		 * </listing>
		 * 
		 * @param nums		an indexed array of numbers.
		 * @return Number.
		 */
		public static function sums(nums:Array):Number
		{
			var sum:Number = 0;
			var numL:Number = nums.length;
			for(var i:Number = 0; i < numL; i++) sum += Number(nums[ i ]);
			return sum;
		}
		
		/**
		 * Report the average of an indexed array of numbers.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.average([ 50, 30, 20 ]);
		 * </listing>
		 * 
		 * @param nums		an indexed array of numbers.
		 * @return Number.
		 */
		public static function average(nums:Array):Number
		{
			return sums(nums) / nums.length;
		}
		
		/**
		 * Checks if the passed-in integer is odd.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.isOdd(23);
		 * </listing>
		 * 
		 * @param n			the integer to check
		 * @return Boolean.
		 */
		public static function isOdd(n:Number):Boolean
		{
			return Boolean(n % 2);
		}
		
		/**
		 * Checks if the passed-in integer is even.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.isOdd(22);
		 * </listing>
		 * 
		 * @param n			the integer to check
		 * @return Boolean.
		 */	
		public static function isEven(n:Number):Boolean
		{
			return (n % 2 == 0);
		}
		
		/**
		 * Checks if the passed-in number is a prime. 
		 * A prime number is a positive integer that has no positive integer 
		 * divisors other than 1 and itself.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.isPrime(22);
		 * </listing>
		 * 
		 * @param n			the number to check.
		 * @return Boolean.
		 */	
		public static function isPrime(n:Number):Boolean
		{
			if(n == 1) return false;
			if(n == 2) return true;
			if(n % 2 == 0) return false;
			for (var i:Number = 3, e:Number = Math.sqrt(n); i <= e; i += 2) if (n % i == 0) return false;
			return true;
		}
		
		/**
		 * Calculates the length of the unknown side of a right triangle, by supplying the 4 points 
		 * that make up the 2 sides. This is the Pythagorean Theorem.
		 *	
		 * @example
		 * <listing>
		 * MathUtil.getHypotenuse(0, 5, 5, 0);
		 * </listing>
		 * 
		 *	@param x1		the x1 point coordinate.
		 *	@param x2		the x2 point coordinate.
		 *	@param y1		the y1 point coordinate.
		 *	@param y2		the y2 point coordinate.
		 *	@return	Number.
		 */
		public static function getHypotenuse(x1:Number, x2:Number, y1:Number, y2:Number):Number
		{
			return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
		}
		
		/**
		 * Format the time.
		 * 
		 * @example
		 * <listing>
		 * MathUtil.formatTime(250);
		 * </listing>
		 * 
		 * @param timeInSeconds		Number.
		 * @return String.
		 */
		public static function formatTime(timeInSeconds:Number):String
		{
			if((!timeInSeconds) || isNaN(timeInSeconds)) return "00:00";
			else
			{
				var minutes:String = Math.floor(timeInSeconds/60).toString();
				var seconds:String = Math.ceil(timeInSeconds % 60).toString();
				
				if(minutes.length == 1) { minutes = "0"+minutes; }
				if(seconds.length == 1) { seconds = "0"+seconds; }
				
				return (minutes+":"+ seconds);
			}
		}
	}
}
